﻿//===============================================================================
// PostSharp Poco implementation for EntityFramework
//===============================================================================
// Copyright © Ruurd Boeke.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos.Weaver;
using PostSharp.CodeModel;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Configuration;
using System.Data.EntityClient;
using System.Collections;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Data.Metadata.Edm;
using System.Collections.ObjectModel;
using PostSharp.Laos;
using PostSharp.CodeWeaver;
using PostSharp.Collections;
using PostSharp.Extensibility;
using EntityFrameworkContrib.PostSharp4EF.Weaver.EDMAttributes;
using System.Reflection;
using PostSharp.CodeModel.Collections;

namespace EntityFrameworkContrib.PostSharp4EF.Weaver
{
    /// <summary>
    /// does the low level weaving of the attributes above properties, and 
    /// implementing the IUpdateComplexTypesWithTracker interface
    /// </summary>
    /// <remarks>does a call to the default value advice</remarks>
    public class PocoEDMWeaver : TypeLevelAspectWeaver
    {
        #region fields and properties
        ModuleDeclaration module;
        IMethod edmScalarPropertyAttribute, edmRelationshipNavigationPropertyAttribute, edmComplexPropertyAttribute;
        private string metadatastring, provider;
        private EDMSubAspect ParentAspect;
        private TypeDefDeclaration typeDef;
        #endregion

        #region ctors and setup
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="containerName">the container of our edm type</param>
        public PocoEDMWeaver(string metadatastring, string provider)
        {
            this.metadatastring = metadatastring;
            this.provider = provider;
        }

        private void CacheNeededInformation()
        {
            // cache some often used stuff
            module = this.Task.Project.Module;
            edmScalarPropertyAttribute = module.FindMethod(typeof(EdmScalarPropertyAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default);

            edmRelationshipNavigationPropertyAttribute =
              module.FindMethod(typeof(EdmRelationshipNavigationPropertyAttribute).GetConstructor(new Type[] { typeof(string), typeof(string), typeof(string) }), BindingOptions.Default);

            edmComplexPropertyAttribute = module.FindMethod(typeof(EdmComplexPropertyAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default);

            ParentAspect = this.TypeLevelAspect as EDMSubAspect;

            // get to our type as it is on disc
            typeDef = (TypeDefDeclaration)this.TargetType;
        }

        /// <inheritdoc />
        public override void EmitCompileTimeInitialization(PostSharp.ModuleWriter.InstructionEmitter writer)
        {
            base.EmitCompileTimeInitialization(writer);
        }

        #endregion

        #region main logic
        /// <summary>
        /// Implements all the things we need to do on this instance
        /// </summary>
        public override void Implement()
        {
            #region setup
            CacheNeededInformation();

            MetadataWorkspace workspace = GetMetaDataWorkspace();

            // we need our entity or complextype so we can loop through it's members
            EntityType entityType;
            ComplexType complexType = null;
            bool IsEntityType = workspace.TryGetItem<EntityType>(this.TargetReflectionType.FullName, DataSpace.CSpace, out entityType);
            if (!IsEntityType)
            {
                complexType = workspace.GetItem<ComplexType>(this.TargetReflectionType.FullName, DataSpace.CSpace);

                // do the complete type attribute
                typeDef.CustomAttributes.Add(CreateComplexTypeAttribute());

            }
            else
            {
                // do the complete type attribute
                typeDef.CustomAttributes.Add(CreateEntityTypeAttribute());
            }


            // do the schema attribute on the assembly
            this.AddSchemaAttributeOnAssemblyIfNeeded();

            // these are the properties we are going to support
            List<EdmMember> propertiesToImplement = new List<EdmMember>();
            foreach (EdmMember mbm in IsEntityType ? entityType.Properties : complexType.Properties)
                propertiesToImplement.Add(mbm);
            if (IsEntityType)
                foreach (NavigationProperty nv in entityType.NavigationProperties)
                    propertiesToImplement.Add(nv);
            #endregion



            #region put EDMScalarAttributes on top of the properties
            foreach (PropertyDeclaration prop in typeDef.Properties)
            {
                EdmProperty memberProperty;
                NavigationProperty navigationProperty;

                // find it as a property
                ReadOnlyMetadataCollection<EdmProperty> properties;
                properties = IsEntityType ? entityType.Properties : complexType.Properties;
                memberProperty = properties.SingleOrDefault(edmprop => edmprop.Name.Equals(prop.Name));

                // it can easily be something else than an edm property
                if (memberProperty != null)
                {
                    // we are looking at a property that should get either the scalar or the complextype attribute

                    if (memberProperty.TypeUsage.EdmType is ComplexType)
                    {
                        prop.CustomAttributes.Add(CreateComplexTypePropertyAttribute());
                    }
                    else
                    {

                        // it might be a key property. I have not yet found a better way to determine if it is a keymember or not. This seems wastefull
                        bool IsKey = IsEntityType ? (entityType.KeyMembers.SingleOrDefault(edmprop => edmprop.Name.Equals(prop.Name)) != null) : false;


                        prop.CustomAttributes.Add(CreateEntityPropertyAttribute(memberProperty, IsKey));
                    }
                    continue;
                }

                if (IsEntityType)
                {
                    // find it as a navigation property
                    navigationProperty = entityType.NavigationProperties.SingleOrDefault(edmprop => edmprop.Name.Equals(prop.Name));
                    if (navigationProperty != null)
                    {
                        // add the relationship edm property
                        prop.CustomAttributes.Add(CreateNavigationPropertyAttribute(navigationProperty));
                    }
                }
            }
            #endregion


            #region put Assembly level attributes for the relationships
            if (IsEntityType)
            {
                AssemblyManifestDeclaration manifest = typeDef.Module.AssemblyManifest;
                CustomAttributeDeclarationCollection atts = manifest.CustomAttributes;
                foreach (NavigationProperty nav in entityType.NavigationProperties)
                {
                    #region check if it has already been added
                    if (atts.FirstOrDefault(cust => cust.ConstructRuntimeObject() is EdmRelationshipAttribute &&
                            ((EdmRelationshipAttribute)cust.ConstructRuntimeObject()).RelationshipName == nav.RelationshipType.Name &&
                            ((EdmRelationshipAttribute)cust.ConstructRuntimeObject()).RelationshipNamespaceName == nav.RelationshipType.NamespaceName) != null)
                    {
                        continue;
                    }
                    #endregion
                    IMethod relAttrCtor =
                      module.FindMethod(typeof(EdmRelationshipAttribute).GetConstructor(new Type[] 
                      { typeof(string), typeof(string), typeof(string), typeof(RelationshipMultiplicity), typeof(Type),
                      typeof(string), typeof(RelationshipMultiplicity), typeof(Type) 
                  }), BindingOptions.Default);


                    // get the actual types
                    // TODO: find out if there is a better way to do this
                    TypeUsage typeUsage = nav.FromEndMember.MetadataProperties.First(prop => prop.Name == "TypeUsage").Value as TypeUsage;
                    string fromMemberTypeName = typeUsage.EdmType.Name.Replace("reference[", "").Replace("]", "");
                    Type fromMemberType = this.TargetReflectionType.Assembly.GetType(fromMemberTypeName);

                    typeUsage = nav.ToEndMember.MetadataProperties.First(prop => prop.Name == "TypeUsage").Value as TypeUsage;
                    string toMemberTypeName = typeUsage.EdmType.Name.Replace("reference[", "").Replace("]", "");
                    Type toMemberType = this.TargetReflectionType.Assembly.GetType(toMemberTypeName);


                    CustomAttributeDeclaration relAttr = new CustomAttributeDeclaration(relAttrCtor);

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 0, "relationshipNamespaceName",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.FindType(typeof(string), BindingOptions.Default)),
                            nav.RelationshipType.NamespaceName)));

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 1, "relationshipName",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.FindType(typeof(string), BindingOptions.Default)),
                            nav.RelationshipType.Name)));

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 2, "role1Name",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.FindType(typeof(string), BindingOptions.Default)),
                            nav.FromEndMember.Name)));

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 3, "role1Multiplicity",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.Cache.GetType(typeof(RelationshipMultiplicity))),
                            (int)nav.FromEndMember.RelationshipMultiplicity)));

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 4, "role1Type",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.FindType(typeof(Type), BindingOptions.Default)),
                            this.module.FindType(fromMemberType, BindingOptions.Default))));

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 5, "role2Name",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.FindType(typeof(string), BindingOptions.Default)),
                            nav.ToEndMember.Name)));

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 6, "role2Multiplicity",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.Cache.GetType(typeof(RelationshipMultiplicity))),
                            (int)nav.ToEndMember.RelationshipMultiplicity)));

                    relAttr.ConstructorArguments.Add(new MemberValuePair(MemberKind.Parameter, 7, "role2Type",
                        new SerializedValue(SerializationType.GetSerializationType(this.module.FindType(typeof(Type), BindingOptions.Default)),
                            this.module.FindType(toMemberType, BindingOptions.Default))));

                    atts.Add(relAttr);
                    //[assembly: EdmRelationshipAttribute("EntityFrameworkContrib.PostSharp4EF.Testing.RelationShipType", "FK_Car_Customer", "Customer",                         RelationshipMultiplicity.ZeroOrOne, typeof(EntityFrameworkContrib.PostSharp4EF.Testing.RelationShipType.Customer), "Car",                                  RelationshipMultiplicity.Many, typeof(EntityFrameworkContrib.PostSharp4EF.Testing.RelationShipType.Car))]
                }
            }
            #endregion


            #region set default values. not yet emitting the instruction, but waiting for the Weave method
            Dictionary<FieldDefDeclaration, object> FieldsNeedingDefaultValue = new Dictionary<FieldDefDeclaration, object>();
            foreach (FieldDefDeclaration field in typeDef.Fields)
            {
                // we have to make concessions: we do not know how to find the field with the property exactly
                EdmProperty memberProperty;

                // find it as a member
                // the rules: the field must match the ending of the propertyname. So underscore is okay
                ReadOnlyMetadataCollection<EdmMember> members = IsEntityType ? entityType.Members : complexType.Members;
                memberProperty = members.SingleOrDefault(edmprop => field.Name.EndsWith(edmprop.Name, StringComparison.OrdinalIgnoreCase)) as EdmProperty;

                // in case that didn't match, try the autogenerated fieldname
                if (memberProperty == null)
                {
                    memberProperty = members.SingleOrDefault(edmprop => (field.Name.IndexOf("<" + edmprop.Name + ">") == 0)) as EdmProperty;
                }

                // if this field belongs to a edm property, we can check for it's default value
                if (memberProperty != null)
                {
                    FieldsNeedingDefaultValue.Add(field, memberProperty.Default);
                }

            }
            // add aspect to do that piece of weaving
            this.Task.TypeLevelAdvices.Add(new DefaultValueAdvice(FieldsNeedingDefaultValue, typeDef));
            #endregion

            #region implement iupdateComplexTypesWithTracker
            // let the type inherit from the interface.. this does not yet implement it
            ITypeSignature updateInterface = module.FindType(typeof(IUpdateComplexTypesWithTracker), BindingOptions.Default);
            TypeSpecDeclaration updateInterfaceSpec = module.TypeSpecs.GetBySignature(updateInterface, true);

            this.typeDef.InterfaceImplementations.Add(updateInterfaceSpec);

            if (typeof(IUpdateComplexTypesWithTracker).GetMethods().Count() != 1 ||
                typeof(IUpdateComplexTypesWithTracker).GetMethods()[0].Name != "UpdateComplexTypesWithTracker")
            {
                throw new NotSupportedException("Weaver expects only one method on IUpdateComplexTypesWithTracker, named UpdateComplexTypesWithTracker");
            }

            MethodSignature implementMethodSign = new MethodSignature(module);
            implementMethodSign.ParameterTypes.Add(module.FindType(typeof(IEntityChangeTracker), BindingOptions.Default));

            // methodref of what we will be implementing
            IMethod updateMethodRef = updateInterfaceSpec.MethodRefs.GetMethod(
                typeof(IUpdateComplexTypesWithTracker).GetMethods()[0].Name, implementMethodSign, BindingOptions.Default);


            // now create the implementation in IL
            MethodDefDeclaration updateMethod = new MethodDefDeclaration();
            // one method
            updateMethod.Name = typeof(IUpdateComplexTypesWithTracker).GetMethods()[0].Name;
            updateMethod.Attributes =   MethodAttributes.Private | MethodAttributes.HideBySig |
                                        MethodAttributes.NewSlot | MethodAttributes.Virtual |
                                        MethodAttributes.Final;
            updateMethod.InterfaceImplementations.Add(updateMethodRef);  // let IL know that we are going to implement this method in the interface
            this.typeDef.Methods.Add(updateMethod);

            ParameterDeclaration tracker = new ParameterDeclaration(0, "tracker", module.FindType(typeof(IEntityChangeTracker), BindingOptions.Default));
            updateMethod.Parameters.Add(tracker);

            updateMethod.MethodBody.RootInstructionBlock = updateMethod.MethodBody.CreateInstructionBlock();
            
            

            
            // we are setup. now we want to generate IL for every complextype declared inside this type

            // we need a boolean so we can store if the property was empty
            LocalVariableSymbol IsPropertyNullLocal =
                updateMethod.MethodBody.RootInstructionBlock.DefineLocalVariable(
                module.FindType(typeof(bool), BindingOptions.Default), "IsPropertyNull");

            InstructionWriter writer = this.Task.InstructionWriter;
            InstructionSequence updateComplexTypesSeq = updateMethod.MethodBody.CreateInstructionSequence();
            updateMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(updateComplexTypesSeq, NodePosition.After, null);
            writer.AttachInstructionSequence(updateComplexTypesSeq);


            foreach (PropertyDeclaration prop in typeDef.Properties)
            {

                EdmProperty memberProperty;

                // find it as a property
                ReadOnlyMetadataCollection<EdmProperty> properties;
                properties = IsEntityType ? entityType.Properties : complexType.Properties;
                memberProperty = properties.SingleOrDefault(edmprop => edmprop.Name.Equals(prop.Name));

                if (memberProperty != null)
                {
                    if (memberProperty.TypeUsage.EdmType is ComplexType)
                    {
                        InstructionSequence continueSequence = updateMethod.MethodBody.CreateInstructionSequence();
                        updateMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(continueSequence, NodePosition.After, writer.CurrentInstructionSequence);

                        PropertyInfo pinfo = prop.GetReflectionWrapper(null, null);
                        MethodInfo getter = pinfo.GetGetMethod(true);

                        // generate IL to set the tracker of this property:
                        writer.EmitInstruction(OpCodeNumber.Nop);
                        writer.EmitInstruction(OpCodeNumber.Ldarg_0);  // put 'this' on the stack
                        writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(getter, BindingOptions.Default));
                        writer.EmitInstruction(OpCodeNumber.Ldnull);
                        writer.EmitInstruction(OpCodeNumber.Ceq);   // compare for equality
                        writer.EmitInstruction(OpCodeNumber.Stloc_0);   // store in the local variablestore: IspropertyNull

                        writer.EmitInstruction(OpCodeNumber.Ldloc_0);
                        writer.EmitBranchingInstruction(OpCodeNumber.Brtrue_S, continueSequence);

                        writer.EmitInstruction(OpCodeNumber.Ldarg_0); // this
                        writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(getter, BindingOptions.Default)); // complex type
                        writer.EmitInstructionString(OpCodeNumber.Ldstr, new LiteralString(memberProperty.Name));
                        writer.EmitInstruction(OpCodeNumber.Ldarg_1);
                        writer.EmitInstructionMethod(OpCodeNumber.Callvirt, module.FindMethod(typeof(IComplexType).GetMethod("SetComplexChangeTracker"), BindingOptions.Default));
                        writer.EmitInstruction(OpCodeNumber.Nop);

                        writer.DetachInstructionSequence();
                        writer.AttachInstructionSequence(continueSequence);

                    }
                    continue;
                }
            }

            
            writer.DetachInstructionSequence();

            InstructionSequence updateComplexTypesfinalSequence = updateMethod.MethodBody.CreateInstructionSequence();
            updateMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(updateComplexTypesfinalSequence, NodePosition.After, writer.CurrentInstructionSequence);

            writer.AttachInstructionSequence(updateComplexTypesfinalSequence);
            
            writer.EmitInstruction(OpCodeNumber.Ret);
            writer.DetachInstructionSequence();
            #endregion

            #region implement originalvalue fields
            List<IField> originalValueFields = new List<IField>();

            foreach (EdmMember edmprop in propertiesToImplement)
            {
                // we should not implement original values of base types
                PropertyDeclaration propToGet = null;
                string originalFieldName ="OriginalValue_" + edmprop.Name;
                // first find the type that should host it. It could be a basetype that wasn't weaved yet!

                TypeDefDeclaration t = this.typeDef as TypeDefDeclaration;
                propToGet = t.Properties.FirstOrDefault(decl => decl.Name == edmprop.Name);

                // not found? try basetypes
                while (propToGet == null && t != null)
                {
                    t = t.BaseType as TypeDefDeclaration;
                    propToGet = t.Properties.FirstOrDefault(decl => decl.Name == edmprop.Name);
                }

                // possibly already weaved on a basetype
                bool fieldAlreadyImplemented = false;
                foreach (FieldDefDeclaration f in t.Fields)
                {
                    if (f.Name == originalFieldName)
                    {
                        fieldAlreadyImplemented = true;
                        originalValueFields.Add(f);
                        break;
                    }
                }

                if (!fieldAlreadyImplemented)
                {
                    // it is our duty to host this field
                    FieldDefDeclaration orig_p = new FieldDefDeclaration();
                    t.Fields.Add(orig_p);
                    originalValueFields.Add(orig_p);
                    orig_p.Name = originalFieldName;
                    orig_p.FieldType = propToGet.PropertyType;
                    orig_p.Attributes = System.Reflection.FieldAttributes.Family;   // needs to be accessible to inheritors
                }

            }
            #endregion

            #region implement IStorageAccess
            // Implements set and getvalue methods that go directly into the property
            // so we can access these without using reflection.

            #region setup

            // let the type implement this interface
            ITypeSignature storageInterface = module.FindType(typeof(IStorageAccess), BindingOptions.Default);
            TypeSpecDeclaration storageInterfaceSpec = module.TypeSpecs.GetBySignature(storageInterface, true);

            this.typeDef.InterfaceImplementations.Add(storageInterfaceSpec);


            // we have two methods to implement. Create the signatures
            MethodSignature StorageGetValueSign = new MethodSignature(module);
            StorageGetValueSign.ParameterTypes.Add(module.FindType(typeof(string), BindingOptions.Default));    // the property name
            StorageGetValueSign.ReturnType = module.FindType(typeof(object), BindingOptions.Default);          // returns an object

            MethodSignature StorageSetValueSign = new MethodSignature(module);
            StorageSetValueSign.ParameterTypes.Add(module.FindType(typeof(string), BindingOptions.Default));    // the property name
            StorageSetValueSign.ParameterTypes.Add(module.FindType(typeof(object), BindingOptions.Default));    // the value that is set

            // find references to the methods on the interface
            IMethod StorageGetValueRef = storageInterfaceSpec.MethodRefs.GetMethod(
                "GetValue", StorageGetValueSign, BindingOptions.Default);
            IMethod StorageSetValueRef = storageInterfaceSpec.MethodRefs.GetMethod(
                "SetValue", StorageSetValueSign, BindingOptions.Default);
            #endregion

            #region get value implementation
            // the declaration
            MethodDefDeclaration StorageGetValueMethod = new MethodDefDeclaration();
            StorageGetValueMethod.Name = "GetValue";
            StorageGetValueMethod.Attributes = MethodAttributes.Public | MethodAttributes.HideBySig |
                                                 MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final;
            StorageGetValueMethod.InterfaceImplementations.Add(StorageGetValueRef);
            this.typeDef.Methods.Add(StorageGetValueMethod);

            // the parameters
            ParameterDeclaration propertyNameForGet = new ParameterDeclaration(0, "Propertyname", module.FindType(typeof(string), BindingOptions.Default));
            StorageGetValueMethod.Parameters.Add(propertyNameForGet);

            StorageGetValueMethod.ReturnParameter = new ParameterDeclaration();
            StorageGetValueMethod.ReturnParameter.Attributes = ParameterAttributes.Retval;
            StorageGetValueMethod.ReturnParameter.ParameterType = module.FindType(typeof(object), BindingOptions.Default);

            StorageGetValueMethod.MethodBody.RootInstructionBlock = StorageGetValueMethod.MethodBody.CreateInstructionBlock();

            // local variable store
            LocalVariableSymbol returnObjectForGet =
                StorageGetValueMethod.MethodBody.RootInstructionBlock.DefineLocalVariable(
                module.FindType(typeof(object), BindingOptions.Default), "ReturnObject");       // loc.0
            LocalVariableSymbol PropertynameEqualGet =
                StorageGetValueMethod.MethodBody.RootInstructionBlock.DefineLocalVariable(
                module.FindType(typeof(bool), BindingOptions.Default), "PropertynameEqual");    // loc.1



            List<InstructionSequence> getsequences = new List<InstructionSequence>();
            InstructionSequence getseq = StorageGetValueMethod.MethodBody.CreateInstructionSequence();
            StorageGetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(getseq, NodePosition.After, null);
            getsequences.Add(getseq);
            writer.AttachInstructionSequence(getseq);

            InstructionSequence returnSeq = StorageGetValueMethod.MethodBody.CreateInstructionSequence();

            foreach (EdmMember edmprop in propertiesToImplement)
            {
                writer.DetachInstructionSequence();
                writer.AttachInstructionSequence(getsequences.Last());

                InstructionSequence continueSequence = StorageGetValueMethod.MethodBody.CreateInstructionSequence();
                StorageGetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(continueSequence, NodePosition.After, getsequences.Last());
                getsequences.Add(continueSequence);


                #region real prop
                writer.EmitInstruction(OpCodeNumber.Nop);
                writer.EmitInstruction(OpCodeNumber.Ldarg_1);     // ldarg 1 is the propertyname
                writer.EmitInstructionString(OpCodeNumber.Ldstr, edmprop.Name); // load our real propertyname
                writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(typeof(string).GetMethod("Equals", new Type[] { typeof(string) }), BindingOptions.Default));

                writer.EmitInstruction(OpCodeNumber.Ldc_I4_0);
                writer.EmitInstruction(OpCodeNumber.Ceq);
                writer.EmitInstruction(OpCodeNumber.Stloc_1);       // store it to our bool
                writer.EmitInstruction(OpCodeNumber.Ldloc_1);       // load our bool

                writer.EmitBranchingInstruction(OpCodeNumber.Brtrue_S, continueSequence);

                // we have not branched, so the property exists and we are going to load it
                writer.EmitInstruction(OpCodeNumber.Ldarg_0);       // ldarg 0 is 'this'.. always needed when calling a method
                PropertyDeclaration propToGet = GetCorrespondingPropertyOnType(edmprop, this.typeDef);
                writer.EmitInstructionMethod(OpCodeNumber.Call, propToGet.Members.GetBySemantic(MethodSemantics.Getter).Method);
                if (propToGet.PropertyType.BelongsToClassification(TypeClassifications.ValueType))
                {
                    // need to box 
                    writer.EmitInstructionType(OpCodeNumber.Box, propToGet.PropertyType);
                }
                writer.EmitInstruction(OpCodeNumber.Stloc_0);
                // now branch to the end
                writer.EmitBranchingInstruction(OpCodeNumber.Br, returnSeq);
                #endregion

                writer.DetachInstructionSequence();
                writer.AttachInstructionSequence(getsequences.Last());

                continueSequence = StorageGetValueMethod.MethodBody.CreateInstructionSequence();
                StorageGetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(continueSequence, NodePosition.After, getsequences.Last());
                getsequences.Add(continueSequence);

                #region original value prop
                writer.EmitInstruction(OpCodeNumber.Nop);
                writer.EmitInstruction(OpCodeNumber.Ldarg_1);     // ldarg 1 is the propertyname
                writer.EmitInstructionString(OpCodeNumber.Ldstr, "OriginalValue_" + edmprop.Name); // load our real propertyname
                writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(typeof(string).GetMethod("Equals", new Type[] { typeof(string) }), BindingOptions.Default));

                writer.EmitInstruction(OpCodeNumber.Ldc_I4_0);
                writer.EmitInstruction(OpCodeNumber.Ceq);
                writer.EmitInstruction(OpCodeNumber.Stloc_1);       // store it to our bool
                writer.EmitInstruction(OpCodeNumber.Ldloc_1);       // load our bool

                writer.EmitBranchingInstruction(OpCodeNumber.Brtrue_S, continueSequence);

                // we have not branched, so the property exists and we are going to load it
                writer.EmitInstruction(OpCodeNumber.Ldarg_0);       // ldarg 0 is 'this'.. always needed when calling a method



                writer.EmitInstructionField(OpCodeNumber.Ldfld, originalValueFields.Find(fld => fld.Name == "OriginalValue_" + edmprop.Name));   // put the value on the stack
                if (propToGet.PropertyType.BelongsToClassification(TypeClassifications.ValueType))
                {
                    // need to box 
                    writer.EmitInstructionType(OpCodeNumber.Box, propToGet.PropertyType);
                }
                writer.EmitInstruction(OpCodeNumber.Stloc_0);
                // now branch to the end
                writer.EmitBranchingInstruction(OpCodeNumber.Br, returnSeq);
                #endregion
            }

            writer.DetachInstructionSequence();
            StorageGetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(returnSeq, NodePosition.After, getsequences.Last());
            writer.AttachInstructionSequence(returnSeq);
            writer.EmitInstruction(OpCodeNumber.Ldloc_0);   // load in the return value
            writer.EmitInstruction(OpCodeNumber.Ret);
            writer.DetachInstructionSequence();
            #endregion

            #region set value implementation
            // the declaration
            MethodDefDeclaration StorageSetValueMethod = new MethodDefDeclaration();
            StorageSetValueMethod.Name = "SetValue";
            StorageSetValueMethod.Attributes = MethodAttributes.Public | MethodAttributes.HideBySig |
                                                 MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final;
            StorageSetValueMethod.InterfaceImplementations.Add(StorageSetValueRef);
            this.typeDef.Methods.Add(StorageSetValueMethod);

            // the parameters
            ParameterDeclaration propertyNameForSet = new ParameterDeclaration(0, "Propertyname", module.FindType(typeof(string), BindingOptions.Default));
            ParameterDeclaration ValueForSet = new ParameterDeclaration(1, "Value", module.FindType(typeof(object), BindingOptions.Default));
            StorageSetValueMethod.Parameters.Add(propertyNameForSet);
            StorageSetValueMethod.Parameters.Add(ValueForSet);

            StorageSetValueMethod.MethodBody.RootInstructionBlock = StorageSetValueMethod.MethodBody.CreateInstructionBlock();

            LocalVariableSymbol PropertynameEqualSet =
                StorageSetValueMethod.MethodBody.RootInstructionBlock.DefineLocalVariable(
                module.FindType(typeof(bool), BindingOptions.Default), "PropertynameEqual");    // loc.0


            List<InstructionSequence> setsequences = new List<InstructionSequence>();
            InstructionSequence setseq = StorageSetValueMethod.MethodBody.CreateInstructionSequence();
            StorageSetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(setseq, NodePosition.After, null);
            setsequences.Add(setseq);
            writer.AttachInstructionSequence(setseq);

            InstructionSequence returnSetSeq = StorageSetValueMethod.MethodBody.CreateInstructionSequence();


            foreach (EdmMember edmprop in propertiesToImplement)
            {
                writer.DetachInstructionSequence();
                writer.AttachInstructionSequence(setsequences.Last());

                InstructionSequence continueSequence = StorageSetValueMethod.MethodBody.CreateInstructionSequence();
                StorageSetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(continueSequence, NodePosition.After, setsequences.Last());
                setsequences.Add(continueSequence);


                #region real value
                writer.EmitInstruction(OpCodeNumber.Nop);
                writer.EmitInstruction(OpCodeNumber.Ldarg_1);     // ldarg 1 is the propertyname
                writer.EmitInstructionString(OpCodeNumber.Ldstr, edmprop.Name); // load our real propertyname
                writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(typeof(string).GetMethod("Equals", new Type[] { typeof(string) }), BindingOptions.Default));

                writer.EmitInstruction(OpCodeNumber.Ldc_I4_0);
                writer.EmitInstruction(OpCodeNumber.Ceq);
                writer.EmitInstruction(OpCodeNumber.Stloc_0);       // store it to our bool
                writer.EmitInstruction(OpCodeNumber.Ldloc_0);       // load our bool

                writer.EmitBranchingInstruction(OpCodeNumber.Brtrue_S, continueSequence);

                // we have not branched, so the property exists and we are going to set it
                writer.EmitInstruction(OpCodeNumber.Ldarg_0);       // ldarg 0 is 'this'.. always needed when calling a method
                writer.EmitInstruction(OpCodeNumber.Ldarg_2);       // ldarg 2 is the value object
                PropertyDeclaration propToSet = GetCorrespondingPropertyOnType(edmprop, this.typeDef);
                if (propToSet.PropertyType.BelongsToClassification(TypeClassifications.ValueType))
                {
                    // it's a value type, we need to unbox
                    writer.EmitInstructionType(OpCodeNumber.Unbox_Any, propToSet.PropertyType);
                }
                writer.EmitInstructionMethod(OpCodeNumber.Call, propToSet.Members.GetBySemantic(MethodSemantics.Setter).Method);
                // now branch to the end
                writer.EmitBranchingInstruction(OpCodeNumber.Br, returnSetSeq);
                #endregion

                writer.DetachInstructionSequence();
                writer.AttachInstructionSequence(setsequences.Last());

                continueSequence = StorageSetValueMethod.MethodBody.CreateInstructionSequence();
                StorageSetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(continueSequence, NodePosition.After, setsequences.Last());
                setsequences.Add(continueSequence);


                #region original value
                writer.EmitInstruction(OpCodeNumber.Nop);
                writer.EmitInstruction(OpCodeNumber.Ldarg_1);     // ldarg 1 is the propertyname
                writer.EmitInstructionString(OpCodeNumber.Ldstr, "OriginalValue_" + edmprop.Name); // load our real propertyname
                writer.EmitInstructionMethod(OpCodeNumber.Call, module.FindMethod(typeof(string).GetMethod("Equals", new Type[] { typeof(string) }), BindingOptions.Default));

                writer.EmitInstruction(OpCodeNumber.Ldc_I4_0);
                writer.EmitInstruction(OpCodeNumber.Ceq);
                writer.EmitInstruction(OpCodeNumber.Stloc_0);       // store it to our bool
                writer.EmitInstruction(OpCodeNumber.Ldloc_0);       // load our bool

                writer.EmitBranchingInstruction(OpCodeNumber.Brtrue_S, continueSequence);

                // we have not branched, so the property exists and we are going to set it
                writer.EmitInstruction(OpCodeNumber.Ldarg_0);       // ldarg 0 is 'this'.. always needed when calling a method
                writer.EmitInstruction(OpCodeNumber.Ldarg_2);       // ldarg 2 is the value object
                if (propToSet.PropertyType.BelongsToClassification(TypeClassifications.ValueType))
                {
                    // it's a value type, we need to unbox
                    writer.EmitInstructionType(OpCodeNumber.Unbox_Any, propToSet.PropertyType);
                }

                writer.EmitInstructionField(OpCodeNumber.Stfld,
                    originalValueFields.Find(fld => fld.Name == "OriginalValue_" + edmprop.Name));   // put the value on the stack
                // now branch to the end
                writer.EmitBranchingInstruction(OpCodeNumber.Br, returnSetSeq);
                #endregion
            }

            writer.DetachInstructionSequence();
            StorageSetValueMethod.MethodBody.RootInstructionBlock.AddInstructionSequence(returnSetSeq, NodePosition.After, setsequences.Last());
            writer.AttachInstructionSequence(returnSetSeq);
            writer.EmitInstruction(OpCodeNumber.Ret);
            writer.DetachInstructionSequence();
            #endregion
            #endregion

        }



        #endregion

        #region helper methods

        private void AddSchemaAttributeOnAssemblyIfNeeded()
        {
            CustomAttributeDeclarationCollection atts = typeDef.Module.AssemblyManifest.CustomAttributes;

            if (null == atts.FirstOrDefault(att => att.ConstructRuntimeObject() is EdmSchemaAttribute))
            {
                IMethod edmSchemaAttCtor = module.FindMethod(typeof(EdmSchemaAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default);
                atts.Add(new CustomAttributeDeclaration(edmSchemaAttCtor));
            }
        }


        private MetadataWorkspace GetMetaDataWorkspace()
        {
            EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder();
            builder.Metadata = metadatastring;
            builder.Provider = provider;

            EntityConnection connection = new EntityConnection(builder.ConnectionString);
            // get to the metadata of the type we wish to transform
            return connection.GetMetadataWorkspace();
        }


        /// <summary>
        /// creates the properties for the attribute
        /// </summary>
        /// <param name="edmProperty">the property as defined in the metadata workspace</param>
        /// <param name="IsKeyProperty">boolean, true = this is a keyproperty</param>
        /// <returns>an edm scalar attribute with named properties set to reflect the metadata</returns>
        CustomAttributeDeclaration CreateEntityPropertyAttribute(EdmProperty edmProperty, bool IsKeyProperty)
        {
            CustomAttributeDeclaration attr = new CustomAttributeDeclaration(edmScalarPropertyAttribute);

            // nullable
            attr.NamedArguments.Add(
                new MemberValuePair(MemberKind.Property,
                    0,
                    "IsNullable",
                    new SerializedValue(
                        SerializationType.GetSerializationType(this.module.FindType(typeof(bool), BindingOptions.Default)),
                        edmProperty.Nullable)
                        ));

            // since we need to set the ordinal, take care to set this property last!
            if (IsKeyProperty)
            {
                attr.NamedArguments.Add(
                    new MemberValuePair(MemberKind.Property,
                        1,
                        "EntityKeyProperty",
                        new SerializedValue(
                            SerializationType.GetSerializationType(this.module.FindType(typeof(bool), BindingOptions.Default)),
                            true)
                            ));
            }

            return attr;
        }

        /// <summary>
        /// creates the properties for the attribute
        /// </summary>
        /// <param name="prop">the property as defined in the metadata workspace</param>
        /// <returns>an edm navigation attribute with ctor params set to reflect the metadata</returns>
        CustomAttributeDeclaration CreateNavigationPropertyAttribute(NavigationProperty prop)
        {
            CustomAttributeDeclaration attr = new CustomAttributeDeclaration(edmRelationshipNavigationPropertyAttribute);

            attr.ConstructorArguments.Add(
                new MemberValuePair(MemberKind.Parameter,
                    0,
                    "relationshipNamespaceName",
                    new SerializedValue(
                        SerializationType.GetSerializationType(this.module.FindType(typeof(string), BindingOptions.Default)),
                        prop.RelationshipType.NamespaceName)
                        ));

            attr.ConstructorArguments.Add(
                new MemberValuePair(MemberKind.Parameter,
                    1,
                    "relationshipName",
                    new SerializedValue(
                        SerializationType.GetSerializationType(this.module.FindType(typeof(string), BindingOptions.Default)),
                        prop.RelationshipType.Name)
                        ));

            attr.ConstructorArguments.Add(
                new MemberValuePair(MemberKind.Parameter,
                    2,
                    "targetRoleName",
                    new SerializedValue(
                        SerializationType.GetSerializationType(this.module.FindType(typeof(string), BindingOptions.Default)),
                        prop.ToEndMember.Name)
                        ));
            return attr;
        }

        /// <summary>
        /// Creates the complex type property attribute: EdmComplexPropertyAttribute
        /// </summary>
        /// <returns></returns>
        CustomAttributeDeclaration CreateComplexTypePropertyAttribute()
        {
            CustomAttributeDeclaration attr = new CustomAttributeDeclaration(edmComplexPropertyAttribute);
            return attr;
        }

        /// <summary>
        /// Creates the attribute that should be on top of the type
        /// </summary>
        /// <returns></returns>
        CustomAttributeDeclaration CreateEntityTypeAttribute()
        {
            CustomAttributeDeclaration attr = new CustomAttributeDeclaration(
                module.FindMethod(typeof(EdmEntityTypeAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default));

            attr.NamedArguments.Add(
                new MemberValuePair(MemberKind.Property,
                                    0,
                                    "Name",
                                    new SerializedValue(
                                        SerializationType.GetSerializationType(
                                            this.module.FindType(typeof(string), BindingOptions.Default)),
                                        this.TargetReflectionType.Name)
                    ));

            attr.NamedArguments.Add(
                new MemberValuePair(MemberKind.Property,
                                    1,
                                    "NamespaceName",
                                    new SerializedValue(
                                        SerializationType.GetSerializationType(
                                            this.module.FindType(typeof(string), BindingOptions.Default)),
                                        this.TargetReflectionType.FullName.Substring(0, this.TargetReflectionType.FullName.LastIndexOf('.')))
                    ));

            return attr;
        }

        /// <summary>
        /// Creates the complex type attribute.
        /// </summary>
        /// <returns></returns>
        CustomAttributeDeclaration CreateComplexTypeAttribute()
        {
            CustomAttributeDeclaration attr = new CustomAttributeDeclaration(
                module.FindMethod(typeof(EdmComplexTypeAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default));
            return attr;
        }

        /// <summary>
        /// walk through inheritance to find the property
        /// </summary>
        /// <param name="edmprop"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        private static PropertyDeclaration GetCorrespondingPropertyOnType(EdmMember edmprop, TypeDefDeclaration t)
        {
            string originalTypename = t.Name;
            PropertyDeclaration propToGet = null;
            do
            {
                if (edmprop.DeclaringType.FullName == t.GetReflectionWrapper(null, null).FullName)
                    propToGet = t.Properties.FirstOrDefault(decl => decl.Name == edmprop.Name);


                t = t.BaseType as TypeDefDeclaration;
            } while (t != null && propToGet == null);

            if (propToGet == null)
            {
                throw new ArgumentOutOfRangeException(String.Format("Trying to find a property named {0} on type {1}, but it did not exist",
                    edmprop.Name, originalTypename));
            }

            return propToGet;
        }
        #endregion

    }
}
